/* 
 * File:   SockEndPointAsincronico.cpp
 * Author: max
 * 
 * Created on June 1, 2013, 3:06 PM
 */

#include "server_SockEndPointAsincronico.h"
#include "common_MutexScopeLocker.h"
#include "common_Codec.h"
#include "common_FirmaDigital.h"
#include <string.h>

using namespace TP;
using namespace TP::Comunicacion::Asincronica;

SockEndPointAsincronico::SockEndPointAsincronico(SockEndPoint* pEndPoint){
    
    _endPoint = pEndPoint;
    _canalSegurizado = false;
    _claveSeguridadCanal = "";
    _transmitiendo = false;
}

SockEndPointAsincronico::SockEndPointAsincronico(int &pFileDescriptor, struct sockaddr_in* &pClientAddress){
    
    _endPoint = new SockEndPoint(pFileDescriptor, pClientAddress);
    _canalSegurizado = false;
    _claveSeguridadCanal = "";
    _transmitiendo = false;    
}

SockEndPointAsincronico::SockEndPointAsincronico(int &pFileDescriptor, struct sockaddr_in* &pClientAddress, const int &pTimeout){
    
    _endPoint = new SockEndPoint(pFileDescriptor, pClientAddress, pTimeout);
    _canalSegurizado = false;
    _claveSeguridadCanal = "";
    _transmitiendo = false;    
}

SockEndPointAsincronico::~SockEndPointAsincronico(){
    delete _endPoint;
}

void SockEndPointAsincronico::Escribir (const uint8_t* pData, const size_t &pLongitudData) throw (Excepcion&){
    try
    {
        if (pLongitudData > 0 && NULL != pData)
        {
            string *mensaje = new string(); 
            *mensaje = this->CodificarMensaje (pData, pLongitudData);
            *mensaje = this->FirmarMensaje(*mensaje);
            this->bufferInPush(mensaje);
        }
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}

void SockEndPointAsincronico::Leer (uint8_t* &pData, uint32_t &pLongitudData) throw (Excepcion&){
    try
    {
        if (!this->bufferOutEmpty())
        {
            // Se extrae el primer mensaje de la cola. Se lo decodifica y se lo ubica en pMensaje.
            string* aux = this->bufferOutPop();

            *aux = this->ExraerMensaje(*aux);
            this->DecodificarMensaje (pData, pLongitudData, *aux);

            // Desencola el mensaje de la cola y lo elimina.
            delete aux;
        }
        else
        {
            pData = NULL;
            pLongitudData = 0;
        }
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}

void SockEndPointAsincronico::Iniciar() throw (Excepcion&){
    try
    {
        _transmitiendo = true;
        Thread::Iniciar();
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}

void SockEndPointAsincronico::Finalizar() throw (Excepcion&){
    try
    {
        _transmitiendo = false;
        _endPoint->Finalizar();
        
        Thread::Finalizar();
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}

void SockEndPointAsincronico::tarea (){
    
    while (_transmitiendo)
    {
        try
        {
            // Se lee un menesaje desde el socket y se lo encola en la cola de mensajes saliente.
            
            // Levanto el mensaje en memoria.
            uint8_t* data = NULL;
            size_t longitudData = 0;
            _endPoint->Leer(data, longitudData);

            // Vuelco el mensaje en un string para su almacenamiento.
            string* mensaje = new string();
            for (size_t i = 0; i < longitudData; i++)
            {
                *mensaje += data [i];
            }
            delete [] data;
            
            // Encolo el mensaje.
            if (strcmp ("KAKAKA", mensaje->c_str()) != 0)
            {
                this->bufferOutPush(mensaje);
            }
            else
            {
                int chan = 1;
            }
            
            // Se toma un mensaje de la cola de mensaje a enviar y se lo envia por el socket.
            if (!this->bufferInEmpty())
            {
                string* valor = this->bufferInPop();
                _endPoint->Escribir(reinterpret_cast <const uint8_t*> (valor->c_str()), valor->length());
                delete valor;
            }
            else
            {
                _endPoint->Escribir(reinterpret_cast <const uint8_t*> ("KAKAKA"), 6);
            }
        }
        catch (Excepcion &ex)
        {
            cerr << ex.what() << endl;
            _transmitiendo = false;
            break;
        }
    }
}

bool SockEndPointAsincronico::bufferInEmpty(){
    MutexScopeLocker locker (_mutexBufferIn);
    return _bufferIn.empty();
}

string* SockEndPointAsincronico::bufferInPop(){
    MutexScopeLocker locker (_mutexBufferIn);
    string* value = _bufferIn.front();
    _bufferIn.pop();
    return value;
}

string* SockEndPointAsincronico::bufferInPush(string  *pValor){
    MutexScopeLocker locker (_mutexBufferIn);
    _bufferIn.push(pValor);
    return pValor;
}

bool SockEndPointAsincronico::bufferOutEmpty(){
    MutexScopeLocker locker (_mutexBufferOut);
    return _bufferOut.empty();
}

string* SockEndPointAsincronico::bufferOutPop(){
    MutexScopeLocker locker (_mutexBufferOut);
    string* valor = _bufferOut.front();
    _bufferOut.pop();
    return valor;
}

string* SockEndPointAsincronico::bufferOutPush(string *pValor){
    MutexScopeLocker locker (_mutexBufferOut);
    _bufferOut.push(pValor);
    return pValor;
}

bool SockEndPointAsincronico::CanalSegurizado(){
    return _canalSegurizado;
}

string SockEndPointAsincronico::GetClaveSeguridadCanal(){
    return _claveSeguridadCanal;
}

string SockEndPointAsincronico::SetClaveSeguridadCanal(const string &pValor){
 
    _claveSeguridadCanal = pValor;
    _canalSegurizado = true;
    
    return _claveSeguridadCanal;
}

string SockEndPointAsincronico::ExraerMensaje(const string& pMensaje) throw (Excepcion &){
    
    if (this->CanalSegurizado())
    {
        string firma = pMensaje.substr(0, 32);
        string mensaje = pMensaje.substr(32, pMensaje.length() - 32);
        
        FirmaDigital firmaDigital;
        string firmaEsperada =firmaDigital.FirmaHMAC(mensaje.c_str(), this->GetClaveSeguridadCanal());
        if (firma == firmaEsperada)
        {
            return mensaje;
        }
        else
        {
            throw Excepcion ("Violacion de seguridad del canal.");
        }
    }

    return pMensaje;
}

void SockEndPointAsincronico::DecodificarMensaje (uint8_t* &pData, uint32_t &pLongitudData, const string& pDataCodificada){
    try
    {
        Codec codec;
        codec.decodificar(pData, pLongitudData, pDataCodificada);
    }
    catch (Excepcion &ex)
    {
        pData = NULL;
        pLongitudData = 0;
    }
}

string SockEndPointAsincronico::CodificarMensaje (const uint8_t* pMensaje, const size_t pLongitud){
    
    try
    {
        string data = string("");
        Codec codec;
        codec.codificar(data, pMensaje, pLongitud);
        return data;
    }
    catch (Excepcion &ex)
    {
        return string("");
    }
}

string SockEndPointAsincronico::FirmarMensaje(const string &pMensaje){
    
    if (this->CanalSegurizado())
    {
        FirmaDigital firmaDigital;
        string firma = firmaDigital.FirmaHMAC(pMensaje.c_str(), this->GetClaveSeguridadCanal());

        return (firma + pMensaje);
    }
    
    return pMensaje;
}


